/*
* Copyright (c) 2008, German Buela
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*     * Redistributions of source code must retain the above copyright
*       notice, this list of conditions and the following disclaimer.
*     * Redistributions in binary form must reproduce the above copyright
*       notice, this list of conditions and the following disclaimer in the
*       documentation and/or other materials provided with the distribution.
*     * The name of German Buela may not be used to endorse or promote products
*       derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY GERMAN BUELA "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL GERMAN BUELA BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

package org.gbuela.avant;

import java.io.IOException;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.gbuela.avant.exceptions.CommandNotFoundException;

import org.gbuela.avant.util.CommandClassTranslator;

/**
 * Avant's only servlet that serves as a "front controller". Resolves requests
 * to commands - creates command instances and invokes them passing a command context.
 * Commands set a "next action" on the context. If there are any exceptions, a default
 * error page is used which can be customized.
 * @author gbuela
 */
//@SuppressWarnings("serial")
public class FrontController extends HttpServlet {
    static final String PARAM_COMMAND_PACKAGE = "command-package";
    static final String PARAM_COMMAND_SUFFIX = "command-suffix";
    static final String PARAM_ERROR_PAGE = "error-page";
    static final String PARAM_CNF_PAGE = "cnf-page";
    
    static final String DEFAULT_COMMAND_SUFFIX = ".do";
    static final String DEFAULT_ERROR_PAGE = "/WEB-INF/error.jsp";
    
    private static CommandClassTranslator cmdClassTranslator = null;
    
    private String getCommandPackage()
    {
        return getInitParameter(PARAM_COMMAND_PACKAGE);
    }
    private String getErrorPage()
    {
        String errorPage = getInitParameter(PARAM_ERROR_PAGE);
        if (errorPage == null)
        {
            errorPage = DEFAULT_ERROR_PAGE;
        }
        return errorPage;
    }
    private String getCNFPage()
    {
        String cnfPage = getInitParameter(PARAM_CNF_PAGE);
        if (cnfPage == null)
        {
            cnfPage = DEFAULT_ERROR_PAGE;
        }
        else
        {
            if(!cnfPage.startsWith("/"))
                cnfPage = "/" + cnfPage;
        }
        return cnfPage;
    }
    private Request createServletWrapper(HttpServletRequest request)
    {
        return new HttpRequest(request);
    }
    private String getCommandSuffix()
    {
        String suffix = getInitParameter(PARAM_COMMAND_SUFFIX);
        if (suffix == null)
        {
            suffix = DEFAULT_COMMAND_SUFFIX;
        }
        return suffix;        
    }
    
    private void initTranslator()
    {
        if (cmdClassTranslator == null)
        {
            String commandPackage = getCommandPackage();
            
            if (commandPackage == null)
            {
                throw new RuntimeException(PARAM_COMMAND_PACKAGE + " not set as servlet init param");
            }
            cmdClassTranslator = new CommandClassTranslator(commandPackage, this.getCommandSuffix());
        }
    }

    private boolean isCommandAction(String nextAction)
    {
        return (nextAction.toLowerCase().endsWith(this.getCommandSuffix()));
    }
    private boolean isViewAction(String nextAction)
    {
        return !isCommandAction(nextAction);
    }
    
    /** Processes requests for both HTTP <code>GET</code> and <code>POST</code> methods.
     * @param request servlet request
     * @param response servlet response
     */
    protected void processRequest(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException 
    {
        initTranslator();
        
        try 
        {
            // Infer and build command class name
            final Class cmdClass;
            
            try
            {
                cmdClass = cmdClassTranslator.toClass(request.getServletPath());
            }
            catch(ClassNotFoundException cnf)
            {
                throw new CommandNotFoundException(request.getServletPath());
            }
            
            if(!Command.class.isAssignableFrom(cmdClass))
            {
                throw new RuntimeException("Class " + cmdClass.getName() + " is not a Command");
            }
            
            // Create command & context, execute
            Command command = (Command)cmdClass.newInstance();

            final CommandContext context = new CommandContext(
                    createServletWrapper(request),
                    cmdClassTranslator);
            
            context.init();
            command.execute(context);
            
            // Proceed with next action
            String nextAction = context.getNextAction();
            if (nextAction == null || nextAction.length() == 0 || nextAction.equalsIgnoreCase(CommandContext.REDIRECT_PREFIX))
            {
                throw new RuntimeException(command.getClass().getName() + " didn't set next action");
            }
            
            if (isViewAction(nextAction) && !nextAction.startsWith("/"))
            {
                nextAction = "/WEB-INF/" + nextAction;
            }
            
            if(context.getServeMethod() == CommandContext.FORWARD)
            {
                RequestDispatcher rd = request.getSession().getServletContext().getRequestDispatcher(nextAction);
                rd.forward(request, response);
            }
            else
            {
                nextAction = context.getNextURL();
                response.sendRedirect(nextAction);
            }
        } 
        catch (Exception ex) 
        {
            request.setAttribute("exception", ex);
            RequestDispatcher rd = request.getSession().getServletContext().getRequestDispatcher(
                    (ex instanceof CommandNotFoundException)? getCNFPage():getErrorPage());
            rd.forward(request, response);
        }
    }
    
    // <editor-fold defaultstate="collapsed" desc="HttpServlet methods. Click on the + sign on the left to edit the code.">
    /** Handles the HTTP <code>GET</code> method.
     * @param request servlet request
     * @param response servlet response
     */
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
    throws ServletException, IOException {
        processRequest(request, response);
    }
    
    /** Handles the HTTP <code>POST</code> method.
     * @param request servlet request
     * @param response servlet response
     */
    protected void doPost(HttpServletRequest request, HttpServletResponse response)
    throws ServletException, IOException {
        processRequest(request, response);
    }
    
    /** Returns a short description of the servlet.
     */
    public String getServletInfo() {
        return "Front Controller";
    }
    // </editor-fold>
}
